6.1 Lineare Regression mit Scikit-Learn#

Lernziele#

Lernziele

  • Sie kennen das lineare Regressionsmodell.

  • Sie können erste grundlegende Schritte der Datenvorverarbeitung anwenden:

    • Sie können unvollständige Daten mit dropna aus dem Datensatz entfernen.

    • Sie können Ausreißer mit drop entfernen.

    • Sie können eine Eigenschaft als Input auswählen und mit reshape in Matrixform bringen.

  • Sie können ein lineares Regressionsmodell aus Scikit-Learn laden und mit fit trainieren.

  • Sie können mit dem trainierten Modell und predict eine Prognose abgeben.

Regression kommt aus der Statistik#

In der Statistik beschäftigen sich Mathematikerinnen und Mathematiker bereits seit Jahrhunderten damit, Analyseverfahren zu entwickeln, mit denen experimentelle Daten gut erklärt werden können. Falls wir eine “erklärende” Variable haben und wir versuchen, die Abhängigkeit einer Messgröße von der erklärenden Variable zu beschreiben, nennen wir das Regressionsanalyse oder kurz Regression. Bei vielen Problemen suchen wir nach einem linearen Zusammenhang und sprechen daher von linearer Regression. Mehr Details finden Sie auch bei Wikipedia → Regressionsanalyse.

Etwas präziser formuliert ist lineare Regression ein Verfahren, bei dem es eine Einflussgröße \(x\) und eine Zielgröße \(y\) mit \(M\) Paaren von dazugehörigen Messwerten \((x^{(1)},y^{(1)})\), \((x^{(2)},y^{(2)})\), \(\ldots\), \((x^{(M)},y^{(M)})\) gibt. Dann sollen zwei Koeffizienten \(\omega_0\) und \(\omega_1\) geschätzt werden, so dass möglichst für alle Datenpunkte \((x^{(i)}, y^{(i)})\) die lineare Gleichung \(y^{(i)} = \omega_0 + \omega_1 x^{(i)}\) gilt. Geometrisch ausgedrückt: durch die Daten soll eine Gerade gelegt werden. Da bei den Messungen auch Messfehler auftreten, werden wir die Gerade nicht perfekt treffen, sondern kleine Fehler machen, die wir hier mit \(\varepsilon^{(i)}\) bezeichnen. Wir suchen also die beiden Parameter \(\omega_0\) und \(\omega_1\), so dass

\[y^{(i)} = \omega_0 + \omega_1 x^{(i)} + \varepsilon^{(i)}.\]

Die folgende Grafik veranschaulicht das lineare Regressionsmodell. Die Paare von Daten sind in blau gezeichnet, das lineare Regressionsmodell in rot.

../_images/Linear_regression.svg

Fig. 6 Lineare Regression: die erklärende Variable (= Input oder unabhängige Variable oder Ursache) ist auf der x-Achse, die abhängige Variable (= Output oder Wirkung) ist auf der y-Achse aufgetragen, Paare von Messungen sind in blau gekennzeichnet, das Modell in rot.#

(Quelle: “Example of simple linear regression, which has one independent variable” von Sewaqu. Lizenz: Public domain))

Zu einer Regressionsanalyse gehört mehr als nur die Regressionskoeffizienten zu bestimmen. Daten müssen vorverarbeitet werden, unter mehreren unabhängigen Variablen (Inputs) müssen diejenigen ausgewählt werden, die tatsächlich die Wirkung erklären. Das lineare Regressionsmodell muss trainiert werden und es muss getestet werden. Bei den meisten ML-Modellen gibt es noch Modellparameter, die feinjustiert werden können und die Prognosefähigkeit verbessern.

Im Folgenden erkunden wir einen realistischen Datensatz und bereiten die Daten für das Training eines linearen Regressionsmodells mit Scikit-Learn vor.

Deutscher Gebrauchtwagenmarkt (Autoscout24)#

Um realistische Beispiele zu haben, mit denen wir die lineare Regression erkunden können, laden wir einen Datensatz mit Daten über den deutschen Gebrauchtwagenmarkt von 2011 bis 2021 (Autoscout24). Der Datensatz stammt von Kaggle. Enthalten sind Daten zu

  • mileage: kilometres traveled by the vehicle (= Kilometerstand)

  • make: make of the car (= Marke)

  • model: model of the car (= Modell)

  • fuel: fuel type (= Treibstoffart)

  • gear: manual or automatic (= Getriebe)

  • offerType: type of offer (new, used, …) (= Angebotsart)

  • price: sale price of the vehicle (= Gebrauchtpreis)

  • hp: horse power (= PS)

  • year: the vehicle registration year (= Baujahr)

Wie immer laden wir die Daten und verschaffen uns zunächst einen Überblick.

import pandas as pd

data_raw = pd.read_csv('data/autoscout24-germany-dataset.csv')
data_raw.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 46405 entries, 0 to 46404
Data columns (total 9 columns):
 #   Column     Non-Null Count  Dtype  
---  ------     --------------  -----  
 0   mileage    46405 non-null  int64  
 1   make       46405 non-null  object 
 2   model      46262 non-null  object 
 3   fuel       46405 non-null  object 
 4   gear       46223 non-null  object 
 5   offerType  46405 non-null  object 
 6   price      46405 non-null  int64  
 7   hp         46376 non-null  float64
 8   year       46405 non-null  int64  
dtypes: float64(1), int64(3), object(5)
memory usage: 3.2+ MB

Offensichtlich sind in den Spalten ‘model’, ‘gear’ und ‘hp’ einige Datensätze nicht vollständig. Das erkennen wir daran, dass insgesmt 46.405 Einträge vorliegen, aber in diesen drei Spalten weniger erfasst sind.

Wir machen es uns jetzt einfach und entfernen die nicht vollständigen Daten aus unserem Datensatz mit der Methode .dropna(), siehe Scikit-Learn (Dokumentation dropna). Bei einem echten Industrieprojekt müssten wir dem Problem nachgehen und die fehlenden Daten beschaffen. Sollte das nicht gehen, so müssten wir als nächstes analysieren, warum die Daten fehlen, ob beispielsweise eine Systematik dahintersteckt, und uns dann einen geeigneten Plan machen, wie mit den fehlenden Daten umzugehen ist. Das ist ein eigenständiges Thema innerhalb des ML, auf das wir im nächsten Kapitel noch näher eingehen werden.

data = data_raw.dropna().copy()
data.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 46071 entries, 0 to 46404
Data columns (total 9 columns):
 #   Column     Non-Null Count  Dtype  
---  ------     --------------  -----  
 0   mileage    46071 non-null  int64  
 1   make       46071 non-null  object 
 2   model      46071 non-null  object 
 3   fuel       46071 non-null  object 
 4   gear       46071 non-null  object 
 5   offerType  46071 non-null  object 
 6   price      46071 non-null  int64  
 7   hp         46071 non-null  float64
 8   year       46071 non-null  int64  
dtypes: float64(1), int64(3), object(5)
memory usage: 3.5+ MB

In dem Datensatz gibt es nur vier Eigenschaften, die numerisch sind, also in Form von Zahlen repräsentiert werden. Wir wählen die Eigenschaft Preis als Zielgröße (=abhängige Variable oder Wirkung oder Output).

Als nächstes erkunden wir, wie der Preis abhängig von den Inputs

  • Kilometerstand,

  • Baujahr und

  • PS

ist, indem wir die Daten plotten. Wir fangen mit dem Kilometerstand der Autos an.

import plotly.express as px

fig = px.scatter(data, x = 'mileage', y = 'price', title='Gebrauchtwagenmarkt 2011-2021 (Autoscout24)')
fig.update_layout(
    xaxis_title = 'Kilometerstand [km]',
    yaxis_title = 'Preis (Euro)'
)
fig.show()

Zunächst einmal stören die Ausreißer etwas. Wir suchen zuerst mal nach den Einträgen mit Preisen über eine halbe Mio. Euro.

filter = data.loc[:, 'price'] > 500000
data.loc[filter, :].head()
mileage make model fuel gear offerType price hp year
11753 90 Maybach Pullman Gasoline Automatic Used 717078 630.0 2019
11754 90 Mercedes-Benz S 650 Gasoline Automatic Used 717078 630.0 2019
21675 431 Ferrari F12 Gasoline Automatic Used 1199900 775.0 2017

Wahrscheinlich handelt es sich bei Eintrag 11753 und 11754 ohnehin um das gleiche Fahrzeug. Wir entfernen die drei Einträge mit der drop()-Methode, siehe Pandas Dokumentation (drop).

data = data.drop([11753, 11754, 21675])
fig = px.scatter(data, x = 'mileage', y = 'price', title='Gebrauchtwagenmarkt 2011-2021 (Autoscout24)')
fig.update_layout(
    xaxis_title = 'Kilometerstand [km]',
    yaxis_title = 'Preis (Euro)'
)
fig.show()

Sieht nicht besonders linear aus, eher wie eine Hyperbel. Als nächstes betrachten wir den Preis in Abhängigkeit des Baujahrs.

fig = px.scatter(data, x = 'year', y = 'price', title='Gebrauchtwagenmarkt 2011-2021 (Autoscout24)')
fig.update_layout(
    xaxis_title = 'Baujahr',
    yaxis_title = 'Preis (Euro)'
)
fig.show()

Je jünger, desto teurer, könnte linear sein. Und zuletzt visualisieren wir den Preis abhängig von der PS-Zahl.

fig = px.scatter(data, x = 'hp', y = 'price', title='Gebrauchtwagenmarkt 2011-2021 (Autoscout24)')
fig.update_layout(
    xaxis_title = 'Leistung (PS)',
    yaxis_title = 'Preis (Euro)'
)
fig.show()

Bei dem Input PS scheint es eine lineare Abhängigkeit zu geben. Je mehr PS desto teurer.

Scikit-Learn: LinearRegression#

Die Bestimmung der Koeffizienten \(\omega_0\) und \(\omega_1\) der Geraden \(y^{(i)} = \omega_0 + \omega x^{(i)}\) funktioniert nicht mehr händisch. Natürlich wäre es möglich, solange Steigungen \(\omega_1\) und y-Achsenabschnitte \(\omega_0\) zu raten, bis eine Gerade herauskommt, die annähernd passt. Scikit-Learn liefert uns sogar die beste Gerade, wobei wir noch diskutieren müssen, was “die beste” genau heißt.

Um Scikit-Learn arbeiten zu lassen, importieren wir die linearen Regressionsmodelle LinearRegression aus dem Modul sklearn.linear_model. Danach initialiseren wir das Modell und speichern es in der Variable model.

from sklearn.linear_model import LinearRegression

model = LinearRegression()
/opt/homebrew/Caskroom/miniconda/base/envs/python310/lib/python3.10/site-packages/scipy/__init__.py:146: UserWarning:

A NumPy version >=1.16.5 and <1.23.0 is required for this version of SciPy (detected version 1.25.0

Mit der Methode .fit() werden die Koeffizienten des Modells an die Daten angepasst. Dazu müssen die Daten in einem bestimmten Format vorliegen. Bei den Inputs wird davon ausgegangen, dass mehrere Eigenschaften in das Modell eingehen sollen. Die Eigenschaften stehen normalerweise in den Spalten des Datensatzes. Beim Output erwarten wir zunächst nur eine Eigenschaft, die durch das Modell erklärt werden soll. Daher geht Scikit-Learn davon aus, dass der Input eine Tabelle(Matrix) \(X\) ist, die M Zeilen und N Spalten hat. M ist die Anzahl an Daten, hier also die Anzahl der Autos, und N ist die Anzahl der Eigenschaften, die betrachtet werden sollen. Da wir momentan nur die Abhängigkeit des Preises von der PS-Zahl analysieren wollen, ist \(N=1\). Beim Output geht Scikit-Learn davon aus, dass eine Datenreihe (eindimensionaler Spaltenvektor) vorliegt, die natürlich ebenfalls M Zeilen hat. Wir müssen daher unsere PS-Zahlen noch in das Matrix-Format bringen. Dazu verwenden wir den Trick, dass mit [ [list] ] eine Tabelle extrahiert wird.

X = data[['hp']]
y = data['price']

#  Training des linearen Regressionsmodells
model.fit(X, y);

Nachdem das lineare Regressionsmodell trainiert wurde, können wir die Steigung in dem Attribut .coef_ablesen und den y-Achsenabschnitt in dem Attribut .intercept_.

print('Steigung: ')
print(model.coef_)

print('y-Achsenabschnitt: ')
print(model.intercept_)
Steigung: 
[186.80491242]
y-Achsenabschnitt: 
-8330.026523879089

Damit könnten wir eine Geradengleichung aufstellen und eine Funktion implementieren, um für eine PS-Zahl eine Prognose abzugeben, welchen Verkauspreis das Auto erzielen könnte. Aber tatsächlich hat das Scikit-Learn für uns schon erledigt. Die Methode .predict() berechnet mit den intern gespeicherten Koeffizienten des linearen Regressionsmodells eine Prognose. Für eine PS-Zahl von 80 Ps wird ein Verkaufspreis von

model.predict([[80]])
/opt/homebrew/Caskroom/miniconda/base/envs/python310/lib/python3.10/site-packages/sklearn/base.py:450: UserWarning:

X does not have valid feature names, but LinearRegression was fitted with feature names
array([6614.36646953])

6.614 EUR erzielt. Denken Sie daran, dass eine Matrix als Input übergeben werden muss.

Damit können wir auch eine Wertetabelle für PS-Zahlen von 0 bis 800 PS aufstellen und das Ergebnis zusammen mit den tatsächlichen Verkaufspreisen visualisieren. Zuerst erzeugen wir die X-Werte, für die das trainierte Modell eine Prognose aufstellen soll. Wir hätten gerne 100 X-Werte von 0 bis 800 PS. Dazu nutzen wir aus dem NumPy-Modul den Befehl linspace(start, stopp, anzahl_punkte).

import numpy as np

print(np.linspace(0, 800, 100))
[  0.           8.08080808  16.16161616  24.24242424  32.32323232
  40.4040404   48.48484848  56.56565657  64.64646465  72.72727273
  80.80808081  88.88888889  96.96969697 105.05050505 113.13131313
 121.21212121 129.29292929 137.37373737 145.45454545 153.53535354
 161.61616162 169.6969697  177.77777778 185.85858586 193.93939394
 202.02020202 210.1010101  218.18181818 226.26262626 234.34343434
 242.42424242 250.50505051 258.58585859 266.66666667 274.74747475
 282.82828283 290.90909091 298.98989899 307.07070707 315.15151515
 323.23232323 331.31313131 339.39393939 347.47474747 355.55555556
 363.63636364 371.71717172 379.7979798  387.87878788 395.95959596
 404.04040404 412.12121212 420.2020202  428.28282828 436.36363636
 444.44444444 452.52525253 460.60606061 468.68686869 476.76767677
 484.84848485 492.92929293 501.01010101 509.09090909 517.17171717
 525.25252525 533.33333333 541.41414141 549.49494949 557.57575758
 565.65656566 573.73737374 581.81818182 589.8989899  597.97979798
 606.06060606 614.14141414 622.22222222 630.3030303  638.38383838
 646.46464646 654.54545455 662.62626263 670.70707071 678.78787879
 686.86868687 694.94949495 703.03030303 711.11111111 719.19191919
 727.27272727 735.35353535 743.43434343 751.51515152 759.5959596
 767.67676768 775.75757576 783.83838384 791.91919192 800.        ]

Das trainierte Modell erwartet Daten in demselben Format, mit dem es trainiert wurde. Daher erstellen wir nun mit dem NumPy-Array einen Pandas-DataFrame und lassen dann das trainierte Modell die Preise prognostizieren.

X_predict = pd.DataFrame(np.linspace(0, 800, 100), columns=['hp'])
y_predict = model.predict(X_predict)

import plotly.graph_objects as go

fig = go.Figure()

fig.add_trace(go.Scatter(x = data['hp'], y = data['price'], mode='markers', name='Daten'))
fig.add_trace(go.Scatter(x = X_predict['hp'], y = y_predict, mode='lines', name='Prognose'))
fig.update_layout(
  title='Gebrauchtwagenmarkt 2011-2021 (Autoscout24)',
  xaxis_title = 'Leistung (PS)',
  yaxis_title = 'Preis (Euro)'
)
fig.show()

Zusammenfassung#

In diesem Abschnitt haben Sie das theoretische Modell der linearen Regression kennengelernt. Um mit einem realistischen Datensatz zu arbeiten, haben wir einen Datensatz von Kaggle importiert und die Daten vorverarbeitet. Danach haben wir mit Scikit-Learn ein lineare Regressionsmodell trainiert und damit eine Prognose erstellt.